package client

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"net/http"
	"time"
)

type Client struct {
	http.Client
	header        map[string]string // Custom header map.
	cookies       map[string]string // Custom cookie map.
	authUser      string            // HTTP basic authentication: user.
	authPass      string            // HTTP basic authentication: pass.
	retryCount    int               // Retry count when request fails.
	retryInterval time.Duration     // Retry interval when request fails.
}

// New creates and returns a new HTTP client object.
func New() *Client {
	client := &Client{
		Client: http.Client{
			Transport: &http.Transport{
				// No validation for https certification of the server in default.
				TLSClientConfig: &tls.Config{
					InsecureSkipVerify: true,
				},
				DisableKeepAlives: true,
			},
		},
		header:  make(map[string]string),
		cookies: make(map[string]string),
	}
	return client
}

// SetTimeout sets the request timeout for the client.
func (c *Client) SetTimeout(t time.Duration) *Client {
	c.Client.Timeout = t
	return c
}

// SetHeader sets a custom HTTP header pair for the client.
func (c *Client) SetHeader(key, value string) *Client {
	c.header[key] = value
	return c
}

// SetHeaderMap sets custom HTTP headers with map.
func (c *Client) SetHeaderMap(m map[string]string) *Client {
	for k, v := range m {
		c.header[k] = v
	}
	return c
}

func (c *Client) Get(requestUrl string) (resp *Response, err error) {
	req, _ := c.prepareRequest("GET", requestUrl, nil)
	resp, err = c.callRequest(req)
	return resp, err
}

func (c *Client) Post(requestUrl string, data interface{}) (resp *Response, err error) {
	req, _ := c.prepareRequest("POST", requestUrl, data)
	resp, err = c.callRequest(req)
	return resp, err
}

func (c *Client) prepareRequest(method, url string, data ...interface{}) (req *http.Request, err error) {
	var params string
	if len(data) > 0 {
		if b, err := json.Marshal(data[0]); err != nil {
			return nil, err
		} else {
			params = string(b)
		}
	}
	if method == "GET" {
		var bodyBuffer *bytes.Buffer
		bodyBuffer = bytes.NewBuffer([]byte(params))
		if req, err = http.NewRequest(method, url, bodyBuffer); err != nil {
			return nil, err
		}
	} else {
		// Normal request.
		paramBytes := []byte(params)
		if req, err = http.NewRequest(method, url, bytes.NewReader(paramBytes)); err != nil {
			return nil, err
		}
	}

	// Custom header.
	if len(c.header) > 0 {
		for k, v := range c.header {
			req.Header.Set(k, v)
		}
	}
	// HTTP basic authentication.
	if len(c.authUser) > 0 {
		req.SetBasicAuth(c.authUser, c.authPass)
	}
	return req, nil
}

// callRequest sends request with give http.Request, and returns the responses object.
// Note that the response object MUST be closed if it'll be never used.
func (c *Client) callRequest(req *http.Request) (resp *Response, err error) {
	resp = &Response{
		request: req,
	}
	for {
		if resp.Response, err = c.Do(req); err != nil {
			// The response might not be nil when err != nil.
			if resp.Response != nil {
				if err := resp.Response.Body.Close(); err != nil {
				}
			}
			if c.retryCount > 0 {
				c.retryCount--
				time.Sleep(c.retryInterval)
			} else {
				//return resp, err
				break
			}
		} else {
			break
		}
	}
	return resp, err
}
